#!/usr/bin/env python
# _*_ coding:utf-8 _*_
import hashlib
import json
import os
import re
import requests
import xmind
import logging
from flask import Flask
from xmind2testcase.parser import xmind_to_testsuites

app = Flask(__name__)
ZENTAOHOST = 'http://xx.xx.xx.xx/'


def get_absolute_path(path):
    """
        Return the absolute path of a file

        If path contains a start point (eg Unix '/') then use the specified start point
        instead of the current working directory. The starting point of the file path is
        allowed to begin with a tilde "~", which will be replaced with the user's home directory.
    """
    fp, fn = os.path.split(path)
    if not fp:
        fp = os.getcwd()
    fp = os.path.abspath(os.path.expanduser(fp))
    return os.path.join(fp, fn)


def get_xmind_testsuites(xmind_file):
    """Load the XMind file and parse to `xmind2testcase.metadata.TestSuite` list"""
    xmind_file = get_absolute_path(xmind_file)
    workbook = xmind.load(xmind_file)
    xmind_content_dict = workbook.getData()
    logging.debug("loading XMind file(%s) dict data: %s", xmind_file, xmind_content_dict)

    if xmind_content_dict:
        testsuites = xmind_to_testsuites(xmind_content_dict)
        return testsuites
    else:
        logging.error('Invalid XMind file(%s): it is empty!', xmind_file)
        return []


def get_xmind_testsuite_list(xmind_file):
    """Load the XMind file and get all testsuite in it

    :param xmind_file: the target XMind file
    :return: a list of testsuite data
    """
    xmind_file = get_absolute_path(xmind_file)
    logging.info('Start converting XMind file(%s) to testsuite data list...', xmind_file)
    testsuite_list = get_xmind_testsuites(xmind_file)
    suite_data_list = []

    for testsuite in testsuite_list:
        product_statistics = {'case_num': 0, 'non_execution': 0, 'pass': 0, 'failed': 0, 'blocked': 0, 'skipped': 0}
        for sub_suite in testsuite.sub_suites:
            suite_statistics = {'case_num': len(sub_suite.testcase_list), 'non_execution': 0, 'pass': 0, 'failed': 0, 'blocked': 0, 'skipped': 0}
            for case in sub_suite.testcase_list:
                if case.result == 0:
                    suite_statistics['non_execution'] += 1
                elif case.result == 1:
                    suite_statistics['pass'] += 1
                elif case.result == 2:
                    suite_statistics['failed'] += 1
                elif case.result == 3:
                    suite_statistics['blocked'] += 1
                elif case.result == 4:
                    suite_statistics['skipped'] += 1
                else:
                    logging.warning('This testcase result is abnormal: %s, please check it: %s', case.result, case.to_dict())
            sub_suite.statistics = suite_statistics
            for item in product_statistics:
                product_statistics[item] += suite_statistics[item]

        testsuite.statistics = product_statistics
        suite_data = testsuite.to_dict()
        suite_data_list.append(suite_data)
    logging.info('Convert XMind file(%s) to testsuite data list successfully!', xmind_file)
    return suite_data_list


def get_xmind_testcase_list(xmind_file):
    """Load the XMind file and get all testcase in it

    :param xmind_file: the target XMind file
    :return: a list of testcase data
    """
    xmind_file = get_absolute_path(xmind_file)
    logging.info('Start converting XMind file(%s) to testcases dict data...', xmind_file)
    testsuites = get_xmind_testsuites(xmind_file)
    testcases = []

    for testsuite in testsuites:
        product = testsuite.name
        for suite in testsuite.sub_suites:
            for case in suite.testcase_list:
                case_data = case.to_dict()
                case_data['product'] = product
                case_data['suite'] = suite.name
                testcases.append(case_data)
    logging.info('Convert XMind file(%s) to testcases dict data successfully!', xmind_file)
    return testcases


def xmind_testsuite_to_json_file(xmind_file):
    """Convert XMind file to a testsuite json file"""
    xmind_file = get_absolute_path(xmind_file)
    logging.info('Start converting XMind file(%s) to testsuites json file...', xmind_file)
    testsuites = get_xmind_testsuite_list(xmind_file)
    testsuite_json_file = xmind_file[:-6] + '_testsuite.json'

    if os.path.exists(testsuite_json_file):
        os.remove(testsuite_json_file)
        # logging.info('The testsuite json file already exists, return it directly: %s', testsuite_json_file)
        # return testsuite_json_file

    with open(testsuite_json_file, 'w', encoding='utf8') as f:
        f.write(json.dumps(testsuites, indent=4, separators=(',', ': '), ensure_ascii=False))
        logging.info('Convert XMind file(%s) to a testsuite json file(%s) successfully!', xmind_file, testsuite_json_file)

    return testsuite_json_file


def xmind_testcase_to_json_file(xmind_file):
    """Convert XMind file to a testcase json file"""
    xmind_file = get_absolute_path(xmind_file)
    logging.info('Start converting XMind file(%s) to testcases json file...', xmind_file)
    testcases = get_xmind_testcase_list(xmind_file)
    testcase_json_file = xmind_file[:-6] + '.json'

    if os.path.exists(testcase_json_file):
        os.remove(testcase_json_file)
        # logging.info('The testcase json file already exists, return it directly: %s', testcase_json_file)
        # return testcase_json_file

    with open(testcase_json_file, 'w', encoding='utf8') as f:
        f.write(json.dumps(testcases, indent=4, separators=(',', ': '), ensure_ascii=False))
        logging.info('Convert XMind file(%s) to a testcase json file(%s) successfully!', xmind_file, testcase_json_file)

    return testcase_json_file


def get_case_number(productID, moduleID, zentaoSID):
    """
    @param productID: 产品id
    @param moduleID: 模块id
    @param zentaoSID: sessionID
    @return: 返回该模块的用例个数
    """
    url = ZENTAOHOST + "zentao/testcase-browse-" + str(productID) + "--byModule-" + str(moduleID) + ".json"
    headers = {
        'Cookie': 'device=desktop; lang=zh-cn; zentaosid=' + zentaoSID
    }
    response = requests.request("POST", url, headers=headers)
    case_num = int(json.loads(json.loads(response.text)['data'])['pager']['recTotal'])
    logging.info('#{0}模块用例数量：{1}'.format(moduleID, case_num))
    return case_num


def get_total_casenum(xmind_file, zentaoSID):
    """
    @param xmind_file: xmind
    @param zentaoSID: zantaoid
    @return: 计算所有模块的用例数量
    """
    cases = get_xmind_testcase_list(xmind_file)  # xmind转换成json格式
    productID = cases[0]['product'].split('#')[1][0:-1]  # 只取产品id中的数字
    moduleID = []
    case_num = 0
    for i in range(len(cases)):
        module = cases[i]['suite'].split('#')[1][0:-1]
        if module not in moduleID:
            moduleID.append(module)
    for j in range(len(moduleID)):
        case_num += get_case_number(productID, moduleID[j], zentaoSID)
    logging.info('用例总数：{0}'.format(case_num))
    return case_num


def xmind_to_zentaocase(xmind_file):
    """
    @param xmind_file:xmind
    @return:禅道用例导入格式的数据
    """
    cases = get_xmind_testcase_list(xmind_file)  # xmind转换成json格式
    zentaocase = {}
    exe_type = {1: 'feature', 2: 'performance', 3: 'config', 4: 'install', 5: 'security', 6: 'interface',
                7: 'other'}
    for i in range(len(cases)):
        zentaocase['product[' + str(i + 1) + ']'] = cases[i]['product'].split('#')[1][0:-1]  # 只取产品id中的数字
        zentaocase['keywords[' + str(i + 1) + ']'] = ''
        zentaocase['title[' + str(i + 1) + ']'] = cases[i]['name']
        zentaocase['module[' + str(i + 1) + ']'] = cases[0]['suite'].split('#')[1][0:-1]  # 只取模块id中的数字
        zentaocase['story[' + str(i + 1) + ']'] = ''
        zentaocase['pri[' + str(i + 1) + ']'] = cases[i]['importance']
        zentaocase['type[' + str(i + 1) + ']'] = exe_type[cases[i]['execution_type']]
        zentaocase['stage[' + str(i + 1) + ']'] = ''
        zentaocase['precondition[' + str(i + 1) + ']'] = cases[i]['preconditions']
        for j in range(len(cases[i]['steps'])):
            zentaocase['stepType[' + str(i + 1) + '][' + str(j + 1) + ']'] = 'step'
            zentaocase['desc[' + str(i + 1) + '][' + str(j + 1) + ']'] = cases[i]['steps'][j]['actions']
            zentaocase['expect[' + str(i + 1) + '][' + str(j + 1) + ']'] = cases[i]['steps'][j]['expectedresults']
    return zentaocase


def get_zentao_account(xmind_file):
    """
    @param xmind_file: xmind
    @return: 获取xmindfile根节点note中的禅道用户名和密码
    """
    file = get_absolute_path(xmind_file)
    workbook = xmind.load(file)
    xmind_content_dict = workbook.getData()
    user = xmind_content_dict[0]['topic']['note']
    user = user.replace('，', ',').split(',')
    if len(user) == 2:
        return user[0].strip(), user[1].strip()
    else:
        logging.error("Xmind根节点的账户输入错误！")
        return None


def get_zentao_sessionID(user):
    """
    @param user: 禅道用户名,禅道密码
    @return: 获取禅道sessionID，用于接口使用
    """
    account = user[0]
    password = user[1]
    url = ZENTAOHOST + '/zentao/user-login.html'
    s = requests.session()
    r = s.get(url)
    verify = re.findall(r"name='verifyRand' id='verifyRand' value='(.*?)'  />", r.content.decode('utf-8'))[
        0]  # 获得verifyRand

    # 第一次加密密码，md5加密密码
    pwd1md5 = hashlib.md5()
    pwd1md5.update(password.encode('utf-8'))
    pwd1_result = pwd1md5.hexdigest()
    # 第2次加密，md5+verifyRand加密密码
    pwd2md5 = hashlib.md5()
    pwd2md5.update((pwd1_result + verify).encode('utf-8'))
    pwd2_result = pwd2md5.hexdigest()

    body = {
        "account": account,
        "password": pwd2_result,
        "passwordStrength": 1,
        "verifyRand": verify,
        "keepLogin": 0,
    }
    # 登录禅道
    s.post(ZENTAOHOST + '/zentao/user-login.html', data=body)
    login_page = s.get(ZENTAOHOST + "/zentao/qa/")
    if "我的地盘" in login_page.text:
        logging.info('禅道登录成功！')
        result = s.get(ZENTAOHOST + "/zentao/api-getsessionid.json")  # 获取禅道sessionID
        data = json.loads(json.loads(result.text)['data'])
        return data['sessionID']
    else:
        logging.error('禅道登录失败！请检查Xmind根节点输入的账户！')


def zentaocase_to_zentao(zentaocase, zentaoSID, xmind_file):
    """
    将转换后的用例数据导入至禅道
    @param xmind_file: xmind
    @param zentaoSID: 禅道登录id
    @param zentaocase: 转换后的用例数据
    """
    logging.info('##### 导入前用例数量 #####')
    init_case_num = get_total_casenum(xmind_file, zentaoSID)  # 导入之前计算用例总数
    if zentaocase is not None:
        productID = zentaocase['product[1]']
        url = ZENTAOHOST + "zentao/testcase-showImport-" + productID + ".html"
        payload = zentaocase
        headers = {
            'Cookie': 'device=desktop; lang=zh-cn; theme=default; zentaosid=' + zentaoSID
        }
        requests.request("POST", url, headers=headers, data=payload)
        logging.info('##### 导入后用例数量 #####')
        end_case_num = get_total_casenum(xmind_file, zentaoSID)  # 导入之后计算用例总数
        import_case_num = len(get_xmind_testcase_list(xmind_file))
        if end_case_num - init_case_num > 0:
            if end_case_num - init_case_num == import_case_num:
                logging.info('用例全部导入到禅道！导入数量：{0}'.format(end_case_num - init_case_num))
            else:
                logging.info('用例部分导入到禅道！导入数量：{0}'.format(end_case_num - init_case_num))
        else:
            logging.error('用例导入失败！请检查数据！')


def check_xmind_format(xmind_file):
    flag = False
    suite = get_xmind_testsuite_list(xmind_file)
    if '#' in suite[0]['name']:  # 存在产品id
        if suite[0]['details']:  # 存在账户密码
            module = suite[0]['sub_suites']
            for i in range(len(module)):
                if '#' in module[i]['name']:  # 存在模块id
                    flag = True
                    return flag
                else:
                    app.logger.error('导入失败！禅道模块id为空!')
                    return flag
        else:
            app.logger.error('导入失败！禅道账户、密码为空!')
            return flag
    else:
        app.logger.error('导入失败！禅道产品id为空!')
        return flag


def check_productID_moduleID(xmind_file, zentaoSID):
    """
    校验xmind中的产品id和模块id在禅道中是否存在
    @param xmind_file: xmind
    @param zentaoSID: zentaosid
    @return: True:输入正确；False：id在禅道不存在
    """
    suite = get_xmind_testsuite_list(xmind_file)
    productID = suite[0]['name'].split('#')[1][0:-1]  # 产品id
    module = suite[0]['sub_suites']
    moduleID = []  # 模块id列表
    for i in range(len(module)):
        moduleID.append(module[i]['name'].split('#')[1][0:-1])
    #  校验产品id是否在禅道中存在
    product_url = ZENTAOHOST + "/zentao/product-all.json"
    product_headers = {'Cookie': 'lang=zh-cn; zentaosid=' + zentaoSID}
    product_response = requests.request("GET", product_url, headers=product_headers)
    product_data = json.loads(json.loads(product_response.text)['data'])['products']  # 获取禅道的所有产品id
    if str(productID) in product_data:
        #  校验模块id是否在禅道中存在
        module_url = ZENTAOHOST + "/zentao/testcase-browse-" + productID + ".json"
        module_headers = {'Cookie': 'lang=zh-cn; zentaosid=' + zentaoSID}
        module_response = requests.request("POST", module_url, headers=module_headers)
        module_data = json.loads(json.loads(module_response.text)['data'])['modules']  # 获取禅道该产品下的所有模块id
        flag = False
        for i in range(len(moduleID)):
            if moduleID[i] not in list(module_data.keys()):
                app.logger.error('导入失败！#{0} 不在禅道模块中，请检查！'.format(moduleID[i]))
                break
            else:
                flag = True
        return flag
    else:
        app.logger.error('导入失败！输入的产品id不正确，请检查！')
        return False
